

#include "includes.h"


static int
compare(void *a, void *b)
{
  if (a == b) return YJ_EQ;
  else if (0 == (((int) a | (int) b) & 0x3)) //fixnums
    {
      if (a < b) return YJ_LT;
      if (a > b) return YJ_GT; 
    }
  return YJ_NE;
}


static void
pushInt32(int x)
{
  while (task_ptr->op_stack + 1 >= task_ptr->op_stack_limit)
    {
      grow_op_stack();
    }
  task_ptr->op_stack++;
  if ((x << 2) > -YJ_INTMAX && (x << 2) < YJ_INTMAX)
    {
      *task_ptr->op_stack = (void *) ((x << 2) | TAG_INT);
    }
  else 
    {
      printf("Integer overflow\n");
      exit(1);
    }
}


void
interpret(void *bytecode)
{
  unsigned char *pc = (unsigned char *) bytecode;

  while (*pc != HALT)
    {
      switch(*pc)
	{

	case PRINTSTACK:
	  {
	    pc++;
	    print_stack();
	  }
	  break;

	case STACKALLOC:
	  {
	    task_ptr->op_stack += ((op1arg8 *) pc)->arg;
	    if (task_ptr->op_stack > task_ptr->op_stack_limit)
	      {
		grow_op_stack();
	      }
	    pc += sizeof(op1arg8);
	  }
	  break;

	case CALL:
	  {
	    yjk_bcmem *f = (yjk_bcmem *) pc;

	    struct function_info *fi = function_lookup_by_id(f->fcall.addr);
	    if (!fi)
	      {
		printf("Runtime Error: Function %d not found.\n", f->fcall.addr);
		exit(1);
	      }
	    else if (fi->compiled_address)
	      {
		f->fcall.addr = (int) fi->compiled_address;
		f->fcall.op = CALLCP;
		exit(1);
	      }
	    else if (fi->bytecode_address)
	      {
		f->fcall.addr = (int) fi->bytecode_address;
		f->fcall.op = CALLBC;
		break;
	      }
	  }
	  break;


	case TAILCALL:
	  {
	    yjk_bcmem *f = (yjk_bcmem *) pc;

	    struct function_info *fi = function_lookup_by_id(f->tcall.addr);
	    if (!fi)
	      {
		printf("Runtime Error: Function %d not found.\n", f->tcall.addr);
		exit(1);
	      }
	    else if (fi->compiled_address)
	      {
		printf("Tail Call compiled function.  Not implemented.\n");
		exit(1);
	      }
	    else if (fi->bytecode_address)
	      {
		f->tcall.addr = (int) fi->bytecode_address;
		f->tcall.op = TAILCALLBC;
		break;
	      }
	  }
	  break;


	case CALLCP:
	  {
	    yjk_bcmem *f = (yjk_bcmem *) pc;

	    check_stack_bounds();
	    //for functions with a variable number of arguments:
	    task_ptr->numargs = f->cfcall.numargs;

	    //save op stack frame
	    push(task_ptr->frame);
	    task_ptr->frame = task_ptr->op_stack;

	    yield();
	    
	    f->cfcall.addr();

	    //clean up arguments
	    task_ptr->op_stack -= f->cfcall.numargs;

	    pc += sizeof(f->cfcall);
	  }
	  break;


	case CALLBC:
	  {
	    yjk_bcmem *f = (yjk_bcmem *) pc;

	    check_stack_bounds();
	    
	    //for functions with a variable number of arguments:
	    task_ptr->numargs = f->fcall.numargs;

	    //save op stack frame
	    push(task_ptr->frame);
	    task_ptr->frame = task_ptr->op_stack;

	    yield();

	    interpret((void *) f->fcall.addr);
	    
	    //clean up arguments
	    task_ptr->op_stack -= f->fcall.numargs;

	    pc += sizeof(f->fcall);
	  }
	  break;


	case TAILCALLBC:
	  {
	    yjk_bcmem *f = (yjk_bcmem *) pc;

	    //for functions with a variable number of arguments:
	    task_ptr->numargs = f->tcall.numargs;
	    
	    void **args = task_ptr->op_stack - f->tcall.numargs + 1; //args to copy
	    task_ptr->op_stack = task_ptr->frame - (f->tcall.ncallargs + 1); //destination
	    task_ptr->frame = *task_ptr->frame;
	    
	    int i;
	    for (i = 0; i < f->tcall.numargs; i++)
	      {
		push(args[i]);
	      }

	    //save frame pointer
	    push(task_ptr->frame);
	    task_ptr->frame = task_ptr->op_stack;

	    yield();
	    
	    pc = (char *) f->tcall.addr;
	  }
	  break;


	case RETURN:
	  {
	    task_ptr->retval = pop();
	    task_ptr->op_stack = task_ptr->frame;
	    task_ptr->frame = (void **) pop();
	    return;
	  }
	  break;


	case PUSHRETVAL:
	  {
	    push(task_ptr->retval);
	    pc++;
	  }
	  break;

	case JMP:
	  {
	    pc++;
	    int a = *(int *) pc;
	    if (a == -1)
	      {
		printf("Error: Unresolved jump point\n");
		exit(1);
	      }
	    pc = bytecode + a;
	  }
	  break;

	case JCOND:
	  {
	    pc++;
	    if (pop() == IMM_TRUE) 
	      {
		int a = *(int *) pc;
		if (a == -1)
		  {
		    printf("Error: Unresolved jump point\n");
		    exit(1);
		  }
		pc = bytecode + a;
	      }
	    else
	      {
		pc += 4;
	      }
	  }
	  break;


	case EQ:
	  {
	    pc++;
	    if (YJ_EQ == compare(pop(), pop()))
	      {
		push(IMM_TRUE);
	      }
	    else 
	      {
		push(IMM_FALSE);
	      }
	  }
	  break;
	    

	case NE:
	  {
	    pc++;
	    if (YJ_EQ != compare(pop(), pop()))
	      {
		push(IMM_TRUE);
	      }
	    else
	      {
		push(IMM_FALSE);
	      }
	  }
	  break;
	  

	case GT:
	  {
	    pc++;
	    if (YJ_GT == compare(pop(), pop()))
	      {
		push(IMM_TRUE);
	      }
	    else
	      {
		push(IMM_FALSE);
	      }
	  }
	  break;
	  

	case LT:
	  {
	    pc++;
	    if (YJ_LT == compare(pop(), pop()))
	      {
		push(IMM_TRUE);
	      }
	    else
	      {
		push(IMM_FALSE);
	      }
	  }
	  break;
	  

	case GTE:
	  {
	    pc++;
	    int r = compare(pop(), pop());
	    if (YJ_GT == r || YJ_EQ == r)
	      {
		push(IMM_TRUE);
	      }
	    else
	      {
		push(IMM_FALSE);
	      }
	  }
	  break;


	case LTE:
	  {
	    pc++;
	    int r = compare(pop(), pop());
	    if (YJ_LT == r || YJ_EQ == r)
	      {
		push(IMM_TRUE);
	      }
	    else
	      {
		push(IMM_FALSE);
	      }
	  }
	  break;


	case SPAWN:
	  {
	    pc++;

	    struct function_info *fi = function_lookup_by_id(*(int *) pc);
	    if (!fi)
	      {
		printf("Error: cannot spawn undefined function\n");
		exit(1);
	      }
	    
	    if (fi->compiled_address)
	      {
		pushInt32(create_task(fi->compiled_address, NULL, pop()));
	      }
	    else if (fi->bytecode_address)
	      {
		pushInt32(create_task(&interpret, fi->bytecode_address, pop()));
	      }
	    else 
	      {
		printf("Error: cannot spawn undefined function\n");
		exit(1);
	      }
	    
	    pc += sizeof(int32_t);
	  }
	  break;

	case RECV:
	  {
	    pc++;

	    task_ptr->state = ST_RECV;
	    yield();

	    //process message
	  }
	  break;

	case SEND:
	  {
	    pc++;

	    task_ptr->state = ST_SEND;
	    task_ptr->send_to =	 (void *) (((int) pop()) >> 2);
	    task_ptr->send_arg = pop();

	    printf("Message sent\n");

	    yield();

	  }
	  break;

	  
	case PUSHINT32:
	  {
	    pc++;
	    pushInt32(*(int *) pc);
	    pc += 4;
	  }
	  break;

	case PUSHFLOAT:
	  {
	    YJK_OBJECT o = heap_alloc2(sizeof(YJK_FLOAT), TYPE_FLOAT, task_ptr);

	    o.flt->value = ((yjk_bcmem *) pc)->opdblarg.n;
	    o.tagged.tag = TAG_PTR;

	    push(o.pointer);

	    pc += sizeof(((yjk_bcmem *) pc)->opdblarg);
	  }
	  break;

	case MAKERATIO:
	  {
	    pc++;
	    YJK_OBJECT o = heap_alloc2(sizeof(YJK_RATIO), TYPE_RATIO, task_ptr);
	    
	    o.ratio->numerator = (YJK_OBJECT) pop();
	    o.ratio->denominator = (YJK_OBJECT) pop();
	    o.tagged.tag = TAG_PTR;

	    push(o.pointer);
	  }
	  break;

	case PUSHBIGINT:
	  {
	    pc++;
	    YJK_BIGINT *bi = (YJK_BIGINT *) pc;
	    YJK_OBJECT o = heap_alloc2(BIGINT_SIZE(bi), TYPE_BIGINT, task_ptr);
	    copy_bigint(bi, o.bigint);
	    o.tagged.tag = TAG_PTR;
	    push(o.pointer);
	    pc += BIGINT_SIZE(bi);
	  }
	  break;

	case PUSHSTRING:
	  {
	    pc++;
	    int len = strlen(pc) + 1;

	    //add to heap here.
	    YJK_OBJECT o = heap_alloc2(sizeof(YJK_STRING) + len, TYPE_STRING, task_ptr);
	    memcpy(o.string->data, pc, len);

	    o.tagged.tag = TAG_PTR;
	    push(o.pointer);

	    pc += len;
	  }
	  break;

	case PUSHARG0:
	  {
	    pc++;
	    push(task_ptr->frame[-1]);
	  }
	  break;

	case PUSHARG1:
	  {
	    pc++;
	    push(task_ptr->frame[-2]);
	  }
	  break;

	case PUSHARG2:
	  {
	    pc++;
	    push(task_ptr->frame[-3]);
	  }
	  break;

	case PUSHARG3:
	  {
	    pc++;
	    push(task_ptr->frame[-4]);
	  }
	  break;

	case PUSHARG4:
	  {
	    pc++;
	    push(task_ptr->frame[5]);
	  }
	  break;

	case ADD:
	  {
	    pc++;
	    if (!(check_stack(0) & 0x1 || check_stack(1) & 0x1))
	      {
		pushInt32(((int) pop() >> 2) + ((int) pop() >> 2));
	      }
	    else
	      {
		push(add(pop(), pop()));
	      }
	  }
	  break;

	case SUBTRACT:
	  {
	    pc++;
	    int a = check_stack(0);
	    int b = check_stack(1);
	    if (!(a & 0x1 || b & 0x1))
	      {
		pushInt32((-(int) pop() >> 2) + ((int) pop() >> 2));
	      }
	  }
	  break;

	case MULTIPLY:
	  {
	    pc++;
	    int64_t result = ((int) pop() >> 2) * ((int) pop() >> 2);
	    if (result < YJ_INTMAX && result > -YJ_INTMAX)
	      {
		pushInt32(result);
	      }
	  }
	  break;
	  
	case DIVIDE:
	  {
	    pc++;
	    pushInt32((int) pop() / (int) pop());
	  }
	  break;

	default:
	  {
	    printf("Unrecognized op code: %x\n", *pc);
	    pc++;
	  }
	  break;
	};
    }

  print_ex_stack();
}

